package com.wh1200.mybatis.xmlless.plugin

import com.wh1200.mybatis.xmlless.constant.Strings
import com.wh1200.mybatis.xmlless.inter.CurrentUserInfoProvider
import com.wh1200.mybatis.xmlless.util.TableInfoProvider
import net.sf.jsqlparser.expression.*
import net.sf.jsqlparser.expression.operators.relational.ExpressionList
import net.sf.jsqlparser.parser.CCJSqlParser
import net.sf.jsqlparser.schema.Column
import net.sf.jsqlparser.statement.insert.Insert
import net.sf.jsqlparser.statement.select.SetOperationList
import net.sf.jsqlparser.statement.update.Update
import net.sf.jsqlparser.statement.update.UpdateSet
import net.sf.jsqlparser.statement.values.ValuesStatement
import org.apache.ibatis.executor.statement.StatementHandler
import org.apache.ibatis.plugin.Interceptor
import org.apache.ibatis.plugin.Intercepts
import org.apache.ibatis.plugin.Invocation
import org.apache.ibatis.plugin.Signature
import org.apache.ibatis.reflection.DefaultReflectorFactory
import org.apache.ibatis.reflection.MetaObject
import org.apache.ibatis.reflection.SystemMetaObject
import java.sql.Connection

/**
 *
 * @author 吴昊
 * @date 2023/12/21 15:11
 * @since v4.0.0
 * @version 1.0
 */
@Intercepts(
    Signature(type = StatementHandler::class,
        method = "prepare", args = [Connection::class, Integer::class]),
)
class CurrentUserInterceptor(private val auditService: CurrentUserInfoProvider<*>) : Interceptor {

  companion object {
    val DEFAULT_REFLECTOR_FACTORY = DefaultReflectorFactory()
  }

  override fun intercept(invocation: Invocation): Any {
    val target = invocation.target
    if (target is StatementHandler) {
      val boundSql = target.boundSql
      val newSql = handleSql(boundSql.sql)
      val meta = MetaObject.forObject(
          boundSql,
          SystemMetaObject.DEFAULT_OBJECT_FACTORY,
          SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
          DEFAULT_REFLECTOR_FACTORY
      )
      meta.setValue("sql", newSql)
    }
    return invocation.proceed()
  }

  private fun handleSql(sql: String): String {
    val statement = CCJSqlParser(sql).Statement()
    if (statement is Insert) {
      processInsert(statement)
    } else if (statement is Update) {
      processUpdate(statement)
    }
    return statement.toString()
  }

  private fun processUpdate(update: Update) {
    val tableInfo = TableInfoProvider.getXmllessTableInfoByTableName(update.table.name) ?: return
    val field = tableInfo.tableFields.find { it.isLastModifiedBy } ?: return
    val setUpdateUser = update.updateSets.find { updateSet ->
      updateSet.columns.any { col -> col.columnName == field.column }
    }
    if (setUpdateUser != null) {
      val exp = setUpdateUser.expressions.first()
      if (exp is Column && exp.columnName == Strings.CURRENT_USER_HOLDER) {
        val currentUserId = auditService.getCurrentUserId()
        setUpdateUser.expressions = arrayListOf(
            getCurrentUserExpression(currentUserId)
        )
      }
      return
    }
    update.addUpdateSet(
        UpdateSet().apply {
          this.columns = arrayListOf(Column(field.column))
          val currentUserId = auditService.getCurrentUserId()
          this.expressions = arrayListOf(
              getCurrentUserExpression(currentUserId)
          )
        }
    )
  }

  private fun processInsert(insert: Insert) {
    val select = insert.select ?: return
    val body = select.selectBody
    if (body is SetOperationList) {
      val selects = body.selects
      selects.filterIsInstance<ValuesStatement>().forEach {
        if (it.expressions is ExpressionList) {
          processExpressionList(it.expressions as ExpressionList)
        }
      }
    }
  }

  private fun processExpressionList(expressionList: ExpressionList) {
    expressionList.expressions.forEach {
      it.accept(object : ExpressionVisitorAdapter() {
        override fun visit(row: RowConstructor) {
          row.exprList.expressions = row.exprList.expressions.map { exp ->
            if (exp is Column && exp.columnName == Strings.CURRENT_USER_HOLDER) {
              val currentUserId = auditService.getCurrentUserId()
              getCurrentUserExpression(currentUserId)
            } else {
              exp
            }
          }
        }
      })
    }
  }

  private fun getCurrentUserExpression(currentUserId: Any?): Expression {
    return if (currentUserId.toString().matches("\\d+".toRegex())) {
      LongValue(currentUserId.toString().toLong())
    } else {
      StringValue(currentUserId.toString())
    }
  }


}
